#ifndef NIGHT_ARRAY_SLICE
#define NIGHT_ARRAY_SLICE

#include "core.hpp"
#include "shape.hpp"

namespace night {

namespace array {

template< typename Shape, typename I1=void, typename I2=void, typename I3=void >
struct slice_shape;

template< typename Shape >
struct slice_shape<Shape> {
	typedef typename empty<Shape>::type type;
	static type apply ( Shape const & ) { return type (); }
};

template< typename Shape, int i, typename I2, typename I3 >
struct slice_shape<Shape,index<i>,I2,I3> {
	typedef slice_shape<Shape,I2,I3> slice_t;
	typedef join< typename at<i,Shape>::type, typename slice_t::type > join_t;

	typedef typename join_t::type type;
	static type apply ( Shape const & src ) { return join_t::apply( at<i,Shape>::apply (src), slice_t::apply (src) ); }
};

template<typename Shape,typename I1,typename I2,typename I3>
struct slice_shape {
	typedef slice_shape<Shape,I2,I3> slice_t;

	typedef typename slice_t::type type;
	static type apply ( Shape const & src ) { return slice_t::apply (src); }
};

template< typename Index1=void, typename Index2=void, typename Index3=void, typename Dummy=void >
struct slice_index;


template<typename Tuple> inline size_type select ( size_type const & s, Tuple const & ) { return s; }
template<typename Tuple> inline size_type select ( index<0>, Tuple const & idx ) { return idx.i; }
template<typename Tuple> inline size_type select ( index<1>, Tuple const & idx ) { return idx.j; }
template<typename Tuple> inline size_type select ( index<2>, Tuple const & idx ) { return idx.k; }


template< typename Index1, typename Index2=void, typename Index3=void > struct norm_index;
template< typename Index1, typename Index2, typename Index3 > struct find_index;

template< typename I2, typename I3 >
struct find_index< index<-1>,I2,I3 > {
	static const size_type value = -1;
};

template< int i, typename I2, typename I3 > 
struct find_index< index<i>,I2,I3 > {
	static const size_type value = detail::if_c<
		detail::is_same<index<i>,I2>::value || detail::is_same<index<i>,I3>::value, 
		norm_index<index<i>,I2,I3>, 
		find_index<index<i-1>,I2,I3> >::type::value;
};

template< typename Index1, typename I2, typename I3 > 
struct norm_index { typedef Index1 type; };

template< int i, typename I2, typename I3 >
struct norm_index< index<i>,I2,I3 > {
	static const size_type value = find_index< index<i-1>,I2,I3 >::value + 1;
	typedef index<value> type;
};


struct index_cast_t {
	template< int i > operator index<i> () { return index<i> (); }
};

template< typename T > T & index_cast ( T & src ) { return src; }
template< int i > index_cast_t index_cast ( index<i> ) { return index_cast_t (); }


template< typename Index1 >
struct slice_index<Index1> {
	typename norm_index<Index1>::type i;

	slice_index ( Index1 x ) : i ( index_cast(x) ) {}

	template< typename E, typename Tuple >
	typename E::result_type operator () ( E const & expr, Tuple const & idx ) const {
		return expr.at ( select (this->i,idx) );
	}
};

template< typename Index1, typename Index2 >
struct slice_index<Index1,Index2> {
	typename norm_index<Index1,Index2>::type i;
	typename norm_index<Index2,Index1>::type j;

	slice_index ( Index1 x, Index2 y ) : i ( index_cast(x) ), j ( index_cast(y) ) {}

	template< typename E, typename Tuple >
	typename E::result_type operator () ( E const & expr, Tuple const & idx ) const {
		return expr.at ( select (this->i,idx), select (this->j,idx) );
	}
};

template< typename Index1, typename Index2, typename Index3 >
struct slice_index<Index1,Index2,Index3> {
	typename norm_index<Index1,Index2,Index3>::type i;
	typename norm_index<Index2,Index1,Index3>::type j;
	typename norm_index<Index3,Index1,Index2>::type k;

	slice_index ( Index1 x, Index2 y, Index3 z ) : i ( index_cast(x) ), j ( index_cast(y) ), k ( index_cast(z) ) {}

	template< typename E, typename Tuple >
	typename E::result_type operator () ( E const & expr, Tuple const & idx ) const {
		return expr.at ( select (this->i,idx), select (this->j,idx), select (this->k,idx) );
	}
};


template< typename E, typename R, typename S, typename Index1, typename Index2, typename Index3 > 
struct slice_view : Expr< slice_view<E,R,S,Index1,Index2,Index3>, R, typename slice_shape<S,Index1,Index2,Index3>::type >
{
	typedef slice_shape<S,Index1,Index2,Index3> slice_shape;
	typedef typename slice_view::result_type result_t;
	typedef typename slice_view::shape_type shape_t;
	typedef typename detail::if_c< shape_t::dim, slice_view, result_t >::type type;
	typedef slice_index<Index1,Index2,Index3> index_type;

	typedef iterator<slice_view,shape_t> iterator, const_iterator;

	typename E::value_type expr;
	index_type idx;

	template<typename I1>
	slice_view ( typename E::param_type src, I1 i ) 
		: slice_view::base_type ( slice_shape::apply( src.shape () ) ), expr(src), idx(i) {}
	template<typename I1, typename I2>
	slice_view ( typename E::param_type src, I1 i, I2 j ) 
		: slice_view::base_type ( slice_shape::apply( src.shape () ) ), expr(src), idx(i,j) {}
	template<typename I1, typename I2, typename I3>
	slice_view ( typename E::param_type src, I1 i, I2 j, I3 k ) 
		: slice_view::base_type ( slice_shape::apply( src.shape () ) ), expr(src), idx(i,j,k) {}

	slice_view & operator = ( slice_view const & rhs ) { return this->assign (rhs); }
	template< typename RE > slice_view & operator = ( RE const & rhs ) { return this->assign (rhs); }

	const_iterator begin () const { return const_iterator ( *this, 0 ); }
	const_iterator end () const { return const_iterator ( *this, this->size() ); }

	result_t at ( size_type i ) const { return this->idx ( this->expr, slice_index<size_type> (i) ); }
	result_t at ( size_type i, size_type j ) const { return this->idx ( this->expr, slice_index<size_type,size_type> (i,j) ); }
	result_t at ( size_type i, size_type j, size_type k ) const { return this->idx ( this->expr, slice_index<size_type,size_type,size_type> (i,j,k) ); }

	static result_t apply ( typename E::param_type src, size_type i ) { return src.at (i); }
	static result_t apply ( typename E::param_type src, size_type i, size_type j ) { return src.at (i,j); }
	static result_t apply ( typename E::param_type src, size_type i, size_type j, size_type k ) { return src.at (i,j,k); }

	template<typename I1>
	static slice_view apply ( typename E::param_type src, I1 i ) { return slice_view (src,i); }
	template<typename I1, typename I2>
	static slice_view apply ( typename E::param_type src, I1 i, I2 j ) { return slice_view (src,i,j); }
	template<typename I1, typename I2, typename I3>
	static slice_view apply ( typename E::param_type src, I1 i, I2 j, I3 k ) { return slice_view (src,i,j,k); }

};

} // end namespace night::array

} // end namespace night

#endif
